package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.dto.*;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class GoodsService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    public Goods buildGoods(SpuDTO spuDTO){
        //封装Skus属性
        List<SkuDTO> skuDTOS = itemClient.findSkusBySpuId(spuDTO.getId());
        //new一个list集合里面的map只要skuDTOS中的四个属性 id title price image
        List<Map<String, Object>> skus = new ArrayList<>();
        skuDTOS.forEach(skuDTO -> {
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", skuDTO.getId());
            skuMap.put("title", skuDTO.getTitle());
            skuMap.put("images", skuDTO.getImages());
            skuMap.put("price", skuDTO.getPrice());
            skus.add(skuMap);
        });
        String skusJsonString = JsonUtils.toString(skus);

        //创建sku中价格的set集合
        Set<Long> priceSet = skuDTOS.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //获取规格参数的key所在的规格参数集合
        List<SpecParamDTO> params = itemClient.findParams(null, spuDTO.getCid3(), true);
        //得到SpuDetail
        SpuDetailDTO spuDetailDTO = itemClient.findSpuDetailBySpuId(spuDTO.getId());
        //获取通用规格参数字段
        String genericSpec = spuDetailDTO.getGenericSpec();
        //通用属性转map
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(genericSpec, Long.class, Object.class);
        //获取特有规格参数字段
        String specialSpec = spuDetailDTO.getSpecialSpec();
        //特有属性转map
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<Long, List<String>>>() {});
        //new一个存储需要搜索的规格参数的map
        Map<String, Object> specs = new HashMap<>();
        params.forEach(param -> {
            //获取规格参数的key
            String key = param.getName();
            //封装规格参数的value
            Object value = null;
            if(param.getGeneric()){
                value = genericSpecMap.get(param.getId());
            }else {
                value = specialSpecMap.get(param.getId());
            }
            //把规格参数中所有是数字类型的兑换成区间来存储到索引库中去
            if(param.getNumeric()){
                value = chooseSegment(value, param);
            }
            specs.put(key, value);
        });

        //创建一个Goods文档对象
        Goods goods = new Goods();
        goods.setSkus(skusJsonString);
        goods.setPrice(priceSet);
        goods.setSpecs(specs);
        goods.setId(spuDTO.getId());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setAll(spuDTO.getName()+spuDTO.getBrandName()+spuDTO.getCategoryName());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        return goods;
    }

    //把规格参数中所有是数字类型的兑换成区间来存储到索引库中去
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


    //查询所有spu商品分页信息
    public PageResult<GoodsDTO> goodsPageQuery(SearchRequest request) {
        try {
            //创建一个SearchQuery的构建对象
            NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
            //添加过滤字段项，指定要查询的字段
            searchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
            //添加过滤条件  QueryBuilders可以来构建出任意想要的query对象，比如match，terms
            searchQueryBuilder.withQuery(getBoolQuery(request));
            //分页属性添加
            searchQueryBuilder.withPageable(PageRequest.of(request.getPage()-1, request.getSize()));
            AggregatedPage<Goods> goodsResult = esTemplate.queryForPage(searchQueryBuilder.build(), Goods.class);
            //把goods对象集合转成GoodsDTO对象集合
            List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(goodsResult.getContent(), GoodsDTO.class);
            PageResult<GoodsDTO> goodsDTOPageResult = new PageResult<>(goodsResult.getTotalElements(), goodsResult.getTotalPages(), goodsDTOS);
            return goodsDTOPageResult;
        }catch (Exception e){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
    }

    //该方法用来构建出一个MatchQueryBuilder对象
    private QueryBuilder getBoolQuery(SearchRequest request) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //搜索参数用match查询
        queryBuilder.must(QueryBuilders.matchQuery("all",request.getKey()).operator(Operator.AND));
        //获取所有规格参数
        Map<String, Object> paramsMap = request.getParamsMap();
        paramsMap.entrySet().forEach(params->{
            String key = params.getKey();
            Object value = params.getValue();
            if(key.equals("品牌")){
                key = "brandId";
            }else if(key.equals("分类")){
                key = "categoryId";
            }else {
                key = "specs." + key;
            }
            queryBuilder.filter(QueryBuilders.termQuery(key,value));
        });
        return queryBuilder;
    }

    public Map<String, List<?>> goodsFilter(SearchRequest request) {
        Map<String, List<?>> filterParamsMap = new LinkedHashMap<>();
        //创建一个SearchQuery的构建对象
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        //封装查询条件
        QueryBuilder baseQuery = getBoolQuery(request);
        searchQueryBuilder.withQuery(baseQuery);
        //添加过滤条件
        searchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //设置不查询内容
        searchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //聚合分类
        //给分类聚合起名字
        String categoryName = "categoryName";
        //添加分类聚合
        searchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryName).field("categoryId"));
        //聚合品牌
        //给品牌聚合起名字
        String brandName = "brandName";
        //添加品牌聚合
        searchQueryBuilder.addAggregation(AggregationBuilders.terms(brandName).field("brandId"));
        //查询条件组装完成，开始索引库查询
        AggregatedPage<Goods> goodsFiltersAgg = esTemplate.queryForPage(searchQueryBuilder.build(), Goods.class);
        //获取聚合所有结果
        Aggregations aggregations = goodsFiltersAgg.getAggregations();
        //获取分类聚合结果
        Terms categoryAggregation = aggregations.get(categoryName);
        //获取分类聚合中桶的信息
        //把分类的过滤参数添加到结果map中
        List<Long> categoryIds = handlerCategoryFilterParams(categoryAggregation, filterParamsMap);
        //获取品牌聚合结果
        Terms brandAggregation = aggregations.get(brandName);
        //把品牌过滤参数放入到map结果集中
        handlerBrandFilterParams(brandAggregation, filterParamsMap);
        //注意：这个要到最下面
        //判断分类个数，如果是一个就继续查询规格参数
        if(categoryIds!=null && categoryIds.size()==1){
            //封装规格参数过滤条件到结果map中
            //参数一：后两个参数查询出来的最终聚合结果放入到这个map中
            //参数二：查询的过滤条件
            //参数三：当前分类的id，由分类id可以查询出当前分类下所有的规格参数集合，就得到了聚合的field域
            handlerSpecParamsMap(filterParamsMap, baseQuery, categoryIds.get(0));
        }
        return filterParamsMap;
    }

    //封装规格参数过滤条件到结果map中
    private void handlerSpecParamsMap(Map<String, List<?>> filterParamsMap, QueryBuilder baseQuery, Long cid) {
        //根据分类查询出所有规格参数列表
        List<SpecParamDTO> params = itemClient.findParams(null, cid, true);
        //创建一个SearchQuery的构建对象
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        //封装查询条件
        searchQueryBuilder.withQuery(baseQuery);
        //添加过滤条件
        searchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //设置不查询内容
        searchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //遍历规格参数列表，封装聚合条件
        params.forEach(param->{
            //获取规格参数名
            String paramName = param.getName();
            //得到要聚合的field名称
            String field = "specs."+paramName;
            //添加聚合条件
            searchQueryBuilder.addAggregation(AggregationBuilders.terms(paramName).field(field));
        });
        //查询索引库，得到所有规格参数的聚合结果
        AggregatedPage<Goods> paramsAggs = esTemplate.queryForPage(searchQueryBuilder.build(), Goods.class);
        Aggregations aggregations = paramsAggs.getAggregations();
        //遍历规格参数去解析每一个对应的聚合结果
        params.forEach(param->{
            //获取规格参数名
            String paramName = param.getName();
            //得到对应的聚合结果
            Terms terms = aggregations.get(paramName);
            List<String> paramsList = terms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            //把结果都放入到最终的map中去返回
            filterParamsMap.put(paramName, paramsList);
        });
    }

    //当前方法会返回搜索条件对应的所有分类的id
    public List<Long> handlerCategoryFilterParams(Terms categoryAggregation, Map<String, List<?>> filterParamsMap){
        List<Long> categoryIds = categoryAggregation.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)//把桶里面的每个id转成Number类型
                .map(Number::longValue)
                .collect(Collectors.toList());//把桶里现在每个Number类型的key变成Long类型
        List<CategoryDTO> categoryDTOS = itemClient.findByIds(categoryIds);
        filterParamsMap.put("分类", categoryDTOS);
        return categoryIds;
    }

    public void handlerBrandFilterParams(Terms brandAggregation, Map<String, List<?>> filterParamsMap){
        List<Long> brandIds = brandAggregation.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        List<BrandDTO> brands = itemClient.findBrandsByIds(brandIds);
        filterParamsMap.put("品牌", brands);
    }

    public void addItemToIndex(Long id) {
        //查询出数据库中的Spu对象
        SpuDTO spuDTO = itemClient.findSpuBySpuId(id);
        //把Spu转成索引库中的goods对象
        Goods goods = buildGoods(spuDTO);
        //写入索引库
        goodsRepository.save(goods);
    }


    public void deleteItemFromIndex(Long id) {
        goodsRepository.deleteById(id);
    }
}

